// TC object stub
var TC = {
	state: 'notInitialized'
};
var storageConfig = null;

function reqOptions(callback) {
	chrome.storage.sync.get([StorageKey.ENABLE_PRIORITY, StorageKey.SCAN_TAGS, StorageKey.MOVE_VIDEO_TO_BODY], function (result) {
		storageConfig = result || {};
		callback && callback();
	});
}
// listen to instructions from background page
if (window === window.top) {
	chrome.runtime.onMessage.addListener(function (message, sender, callback) {
		if (message.type === "browserAction") {
			reqOptions(function () {
				switch (TC.state) {
					case 'notInitialized':
						initializeTabCinema();
						// note the fall-through here
					case 'normal':
						TC.findVideos([]);
						break;
					case 'maximized':
						TC.minimizeVideo();
						break;
					case 'overlay':
						TC.removeOverlays();
						break;
				}
			});
		}
	});
}

// listen to events from child/parent windows
var TCrequests = [
	'requestVideos',
	'reportVideos',
	'reportDone',
	'maximizeVideo',
	'minimizeVideo',
	'addOverlay',
	'removeOverlays',
	'removeOverlay',
	'requestEndFullscreen'
];

window.addEventListener("message", function (event) {
	if (TCrequests.indexOf(event.data.message) === -1) {
		// do not respond to messages that do not belong to TC
		return;
	}

	reqOptions(function () {
			// initialize TabCinema upon first message
			if (TC.state === "notInitialized") {
				initializeTabCinema();
			}
			// process message
			switch (event.data.message) {
				case 'requestVideos':
					TC.findVideos(event.data.path);
					break;
				case 'reportDone':
					TC.processReport();
					break;
				case 'reportVideos':
					TC.addVideos(event.source, event.data.videos);
					break;
				case 'maximizeVideo':
					TC.maximizeVideo(event.data.path);
					break;
				case 'minimizeVideo':
					TC.minimizeVideo();
					break;
				case 'addOverlay':
					TC.addOverlay(event.data.uid);
					break;
				case 'removeOverlays':
					TC.removeOverlays();
					break;
				case 'removeOverlay':
					TC.removeOverlay(event.data.uid);
					break;
				case 'requestEndFullscreen':
					TC.minimizeVideo();
					break;
			}
		});
}, false);

// listen to shortcut key
document.body.addEventListener('keydown', function (e) {
	if ((e.keyCode === 32 && e.ctrlKey) || (e.keyCode === 27 && (TC.state === 'maximized' || TC.state === 'overlay'))) {
		switch (TC.state) {
			default:
			case 'notInitialized':
			case 'normal':
				window.top.postMessage({
					message: 'requestVideos',
					path: []
				}, '*');
				break;
			case 'maximized':
				window.top.postMessage({
					message: 'requestEndFullscreen'
				}, '*');
				break;
			case 'overlay':
				window.top.postMessage({
					message: 'removeOverlays'
				}, '*');
				break;
		}
		e.stopPropagation();
		e.preventDefault();
	}
});

// populate TC object
function initializeTabCinema() {
	TC = {

		// options
		options: {
			scanTags: "video,canvas,embed,object,img",
			scanTimeout: 250,
			minSize: 150
		},

		// local variables
		state: "normal",
		path: [],
		allVideos: [],
		myVideos: {},
		iframes: {},
		target: {},
		scrollBeforeMaximize: {
			top: 0,
			left: 0
		},
		handleVideoRemoveTimeout: 0,
		wrapUpTimeout: 0,
		pendingReports: 0,
		inlineStyleSubtle: "\
		clear		: none		!important;\
		top			: 0			!important;\
		left		: 0 		!important;\
		min-width	: 0			!important;\
		min-height	: 0			!important;\
		width		: 100%		!important;\
		height		: 100%		!important;\
		max-width	: 100%		!important;\
		max-height	: 100%		!important;\
		margin		: 0			!important;\
		padding		: 0			!important;\
		visibility	: visible	!important;\
		border-width: 0			!important;\
		background	: black		!important;",
		inlineStyleForce: "\
		position	: fixed 	!important;\
		top			: 0			!important;\
		left		: 0 		!important;\
		min-width	: 0			!important;\
		min-height	: 0			!important;\
		width		: 100%		!important;\
		height		: 100%		!important;\
		max-width	: 100%		!important;\
		max-height	: 100%		!important;\
		margin		: 0			!important;\
		padding		: 0			!important;\
		visibility	: visible	!important;\
		border-width: 0			!important;\
		opacity     : 1			!important;\
		transform   : none		!important;\
		background	: black		!important;",

		// find all videos on this page
		findVideos: function (path) {

			// store path for later reference
			this.path = path;

			// reset collections
			this.allVideos = [];
			this.myVideos = {};
			this.iframes = {};

			// first find all video elements in this frame and report
			var videos = [];
			let scanTags = (storageConfig[StorageKey.SCAN_TAGS] || this.options.scanTags).split(",");
			for (let iTag = 0, lenTag = scanTags.length; iTag < lenTag; ++iTag) {
				var tagname = scanTags[iTag];
				var elmts = document.querySelectorAll(tagname);
				// console.log(`${tagname} ${elmts.length}`);
				for (var i = 0; i < elmts.length; i++) {

					var el = elmts[i];

					// skip tiny elements
					if (Math.min(el.offsetWidth, el.offsetHeight) < this.options.minSize) {
						continue;
					}

					// skip objects that are invisible or not currently in the viewport
					if (this.fractionInViewport(el) === 0) {
						continue;
					}

					// be careful with objects containing embeds (common flash construction)
					var tag = el.nodeName.toLowerCase();
					if (tag === 'embed') {
						if (el.parentNode.nodeName.toLowerCase() === 'object') {
							if (Math.min(el.parentNode.offsetWidth, el.parentNode.offsetHeight) > this.options.minSize) {
								// parent object will be scanned in due time; skip this embed
								continue;
							}
						}
					} else if (tag === 'object') {
						var embeds = el.getElementsByTagName('embed');
						if (embeds.length !== 0) {
							// always scale the embed rather than the object
							el = embeds[0];
						}
					}

					// generate (semi) unique path
					var uid = '' + Math.round(Math.random() * 1e9);

					// add to videos collection to forward to main page
					videos.push({
						uid: uid,
						path: this.path.concat([uid]),
						tag: tag
					});

					// add to private video collection
					this.myVideos[uid] = el;

				}
				// console.log(`${videos.length}`);
				// tag privilege
				if (storageConfig[StorageKey.ENABLE_PRIORITY]) {
					if (videos.length > 0) break;
				}
			}

			// send videos collection to wrap-up function
			if (videos.length > 0) {
				if (window === window.top) {
					this.addVideos(window, videos);
				} else {
					// report results to top window
					window.top.postMessage({
						message: 'reportVideos',
						videos: videos
					}, '*');
				}
			}

			// then tell all iframes to report their videos
			var iframes = document.querySelectorAll("iframe");
			this.pendingReports = 0;
			for (var i = 0; i < iframes.length; i++) {
				var frame = iframes[i];
				// only scan iframes that are currently in the content window
				if (this.fractionInViewport(frame) > 0) {
					var uid = '' + Math.round(Math.random() * 1e9);
					this.iframes[uid] = frame;
					this.pendingReports++;
					frame.contentWindow.postMessage({
						message: 'requestVideos',
						path: this.path.concat([uid])
					}, '*');
				}
			}

			// wrap up reports if there are no iframes, or set a timeout
			if (this.pendingReports === 0) {
				this.wrapUpReports();
			} else {
				this.wrapUpTimeout = window.setTimeout(function () {
					console.warn('TC iframe timeout');
					TC.wrapUpReports();
				}, this.options.scanTimeout);
			}
		},

		// top window: handle reports of videos in iframes
		addVideos: function (parentWindow, videos) {
			for (var i in videos) {
				videos[i].window = parentWindow;
			}
			this.allVideos = this.allVideos.concat(videos);
		},

		// iframe reporting in
		processReport: function () {
			if (--this.pendingReports === 0) {
				window.clearTimeout(this.wrapUpTimeout);
				this.wrapUpReports();
			}
		},

		// after all reports have come in, or timeout occured, take action
		wrapUpReports: function () {

			if (window !== window.top) {
				// signal parent that we're finished here
				window.parent.postMessage({
					message: 'reportDone'
				}, '*');
			} else {

				// all reports are in... find out which video to maximize
				if (this.allVideos.length === 1) {

					// only one candidate -> maximize that one
					this.maximizeVideo(this.allVideos[0].path);

				} else if (this.allVideos.length > 1) {

					// multiple candidates -> videos take precedence
					var videoCount = 0,
						videoIndex = 0;
					for (var i = 0; i < this.allVideos.length; i++) {
						if (this.allVideos[i].tag !== 'img') {
							videoCount++;
							videoIndex = i;
						}
					}

					if (videoCount === 1) {

						// only one video -> maximize that
						this.maximizeVideo(this.allVideos[videoIndex].path);

					} else {

						// multiple or zero video elements -> let user choose via overlays
						this.addOverlays();

					}
				} else {

					// no videos or images found
					if (this.stage === "maximized") {
						window.top.postMessage({
							message: 'requestEndFullscreen'
						}, '*');
					}

				}

			}

		},

		// start the maximizing process for a single video
		maximizeVideo: function (path) {

			// state-dependent first action
			if (this.state === "maximized") {

				// was maximized already, clean up remnants of current video
				if (this.target.tag === "video" && !this.target.isYTHTML5) {
					this.target.customControls.destroy();
				}

			} else {

				// new maximize, remember scroll position
				this.scrollBeforeMaximize = {
					top: document.body.scrollTop,
					left: document.body.scrollLeft
				};

				// remove overlays if any
				if (this.state === "overlay") {
					this.removeOverlays();
				}
			}

			// take action depending on iframe or element
			if (path.length > 1) {

				// set frame CSS
				this.target = {
					DOMnode: this.iframes[path[0]],
					tag: 'iframe',
					subtle: false
				};
				this.maximizeTarget();

				// communicate down
				this.iframes[path[0]].contentWindow.postMessage({
					message: 'maximizeVideo',
					path: path.slice(1)
				}, '*');

			} else {

				// collect target from myVideos collection
				var el = this.myVideos[path[0]];

				// set target properties
				this.target = {
					DOMnode: el,
					tag: el.nodeName.toLowerCase(),
					player: this.getPlayer(el),
					quality: this.getQuality(el),
					subtle: true
				};

				// replace scaling variable
				if (this.target.tag === 'object') {
					var params = this.target.DOMnode.getElementsByTagName('param');
					for (var i = 0; i < params.length; i++) {
						if (params[i].name === 'scale') {
							if (params[i].value === 'noscale') {
								this.target.subtle = false;
								this.target.scaleParam = params[i].value;
								params[i].value = 'default';
							}
						} else if (params[i].name === 'flashvars') {
							var fv = params[i].value;
							if (fv) {
								var newfv = fv.replace(/stretching=[^&]*/i, 'stretching=uniform');
								if (newfv !== fv) {
									this.target.subtle = false;
									this.target.fv = fv;
									params[i].value = newfv;
								}
							}
						}
					}
				} else if (this.target.tag === 'embed') {
					var fv = this.target.DOMnode.getAttribute('flashvars');
					if (fv) {
						this.target.fv = fv;
						var newfv = fv.replace(/stretching=[^&]*/i, 'stretching=uniform');
						this.target.DOMnode.setAttribute('flashvars', newfv);
					}
				}

				// change quality
				this.setQuality(this.target.DOMnode, 'highest');

				// add custom controls to HTML5 video
				if (this.target.tag === 'video') {

					// do not replace yt html5 controls as this messes up the buffering
					if (window.location.host === "www.youtube.com") {
						var controls = document.querySelector('.html5-video-controls');
						if (controls) {
							this.target.isYTHTML5 = true;
							this.target.YTHTML5controls = controls;
							this.target.controlsVisible = false;
							if (typeof controls['originalStyle'] === "undefined") {
								controls['originalStyle'] = controls.getAttribute('style') || '';
								controls.setAttribute('style', controls['originalStyle'] + 'position: absolute !important; visibility: hidden !important;');
								TC.addClassDeep(controls, 'tc-show');
							}
							var hideControlsTimeout = 0;
							var onControls = false;
							document.body.addEventListener('mousemove', this.handleYTHTML5MouseMove);
						}
					}

					// hide standard controls and add custom HTML5 controls to circumvent click handler issues
					if (!this.target.isYTHTML5) {
						this.target.subtle = false;
						this.target.controls = this.target.DOMnode.controls;
						this.target.DOMnode.controls = false;
						this.target.customControls = this.createHTML5Controls(this.target.DOMnode);
						this.target.DOMnode.parentNode.appendChild(this.target.customControls.container);
					}
				}

				// add css to element
				this.maximizeTarget();

				// set focus to video
				try {
					this.target.DOMnode.focus();
				} catch (err) {}

			}

			// scroll to top left
			document.body.scrollTop = document.body.scrollLeft = 0;

			// take actions that are not to be repeated when maximizing next video in playlist
			if (this.state !== "maximized") {

				// create an observer instance to register changes in the document while maximized
				this.removedObserver = new MutationObserver(this.handleMutations);
				this.removedObserver.observe(document.body, {
					childList: true,
					subtree: true
				});

				// set new state
				this.state = "maximized";

			}

		},

		// Maximize action DOM manipulations
		maximizeTarget: function () {

			var el = this.target.DOMnode;

			// for images, simply set the image as the body background
			if (this.target.tag === 'img') {
				var src = el.src || '';
				el = document.body;
				while (el !== document) {

					// make sure element remains visible
					el.classList.add('tc-show');

					// maximize all parents if in subtle mode
					if (typeof el['originalStyle'] === "undefined") {
						el['originalStyle'] = el.getAttribute('style') || '';
					}

					// append style rather than replacing it, in case element has inline properties that might trigger reload when removed (e.g. position)
					if (el === document.body) {
						el.setAttribute('style', el['originalStyle'] + this.inlineStyleSubtle + 'background: black url("' + src + '") no-repeat center center fixed !important;  background-size: contain !important;');
					} else {
						el.setAttribute("style", el['originalStyle'] + this.inlineStyleSubtle);
					}

					// move up one level
					el = el.parentNode;
				}
				return;
			}

			// be more subtle with embeds and objects to avoid triggering reloads
			if (!this.target.subtle) {
				if (typeof el['originalStyle'] === "undefined") {
					el['originalStyle'] = el.getAttribute('style') || '';
				}
				el.setAttribute("style", this.inlineStyleForce);
			}

			while (el !== document) {

				// make sure element remains visible
				el.classList.add('tc-show');

				// maximize all parents if in subtle mode
				if (this.target.subtle) {
					el.classList.add('tc-subtle');
					if (typeof el['originalStyle'] === "undefined") {
						el['originalStyle'] = el.getAttribute('style') || '';
					}
					// append style rather than replacing it, in case element has inline properties that might trigger reload when removed (e.g. position)
					el.setAttribute("style", el['originalStyle'] + this.inlineStyleSubtle);
				}

				// move up one level
				el = el.parentNode;

			}
			// 记录 el 的原始父节点，并强制把 el 添加到 body 中
			if (storageConfig[StorageKey.MOVE_VIDEO_TO_BODY] && this.target.DOMnode.parentNode && this.target.DOMnode.parentNode.parentNode) {
				this.target.DOMnode.__originalParentNode__ = this.target.DOMnode.__originalParentNode__ || this.target.DOMnode.parentNode.parentNode;
				document.body.appendChild(this.target.DOMnode.parentNode);
			}
		},

		// Minimize the active video
		minimizeVideo: function () {

			// restore state
			this.state = "normal";

			// stop observing DOM changes
			this.removedObserver.disconnect();

			// restore object parameters
			if (typeof this.target.scale !== "undefined") {
				this.target.DOMnode.scale = this.target.scale;
			}

			// tag-specific restorations
			if (this.target.tag === "iframe") {
				this.target.DOMnode.contentWindow.postMessage({
					message: 'minimizeVideo'
				}, '*');
			} else if (this.target.tag === "video") {
				if (this.target.isYTHTML5) {
					window.clearTimeout(TC.hideControlsTimeout);
					document.body.removeEventListener('mousemove', this.handleYTHTML5MouseMove);
					var controls = this.target.YTHTML5controls;
					controls.setAttribute('style', controls['originalStyle']);
					TC.removeClassDeep(controls, 'tc-show');
				} else {
					this.target.customControls.destroy();
					this.target.DOMnode.controls = this.target.controls;
				}
			} else if (this.target.tag === 'embed') {
				if (this.target.fv) {
					this.target.DOMnode.setAttribute('flashvars', this.target.fv);
				}
			} else if (this.target.tag === 'object') {
				if (this.target.scaleParam || this.target.fv) {
					var params = this.target.DOMnode.getElementsByTagName('param');
					for (var i = 0; i < params.length; i++) {
						if (this.target.scaleParam && params[i].name === 'scale') {
							params[i].value = this.target.scaleParam;
						} else if (this.target.fv && params[i].name === 'flashvars') {
							params[i].value = this.target.fv;
						}
					}
				}
			}

			// restore elements
			this.minimizeTarget();

			//restore scroll position
			document.body.scrollTop = this.scrollBeforeMaximize.top
			document.body.scrollLeft = this.scrollBeforeMaximize.left;

			// reset collections
			this.allVideos = [];
			this.myVideos = {};
			this.iframes = {};
		},

		// Minimize video DOM actions
		minimizeTarget: function () {

			if (!this.target.DOMnode) {
				// this can occur if the video was removed
				return;
			}

			// restore original parent node
			if (this.target.DOMnode.__originalParentNode__) {
				this.target.DOMnode.__originalParentNode__.appendChild(this.target.DOMnode.parentNode);
			}

			// for images, simply set the image as the body background
			if (this.target.tag === 'img') {
				el = document.body;
				while (el !== document) {

					// make sure element remains visible
					el.classList.remove('tc-show');

					el.setAttribute('style', el['originalStyle']);
					el.removeAttribute('originalStyle');

					// move up one level
					el = el.parentNode;
				}
				return;
			}

			// restore target css
			var el = this.target.DOMnode;
			if (!this.target.subtle) {
				el.setAttribute('style', el['originalStyle']);
				el.removeAttribute('originalStyle');
			}

			while (el && el !== document) {

				// remove added class
				el.classList.remove('tc-show');

				// restore css
				if (this.target.subtle) {
					el.classList.remove('tc-subtle');
					el.setAttribute('style', el['originalStyle']);
					el.removeAttribute('originalStyle');
				}

				// move up one level
				el = el.parentNode;
			}
		},

		// show a button on top of each video
		addOverlays: function () {
			this.state = 'overlay';
			for (var i = 0; i < this.allVideos.length; i++) {
				var video = this.allVideos[i];
				if (video.window === window) {
					this.addOverlay(video.uid);
				} else {
					video.window.postMessage({
						message: 'addOverlay',
						uid: video.uid
					}, '*');
				}
			}
		},

		// remove overlays over videos
		removeOverlays: function () {
			this.state = 'normal';
			for (var i = 0; i < this.allVideos.length; i++) {
				var video = this.allVideos[i];
				if (video.window === window) {
					this.removeOverlay(video.uid);
				} else {
					video.window.postMessage({
						message: 'removeOverlay',
						uid: video.uid
					}, '*');
				}
			}
		},

		// create and insert overlay button
		addOverlay: function (uid) {
			var el = this.myVideos[uid];
			var div = document.createElement('div');
			div.classList.add('tc-overlay');
			div.style.width = el.offsetWidth + 'px';
			div.style.height = el.offsetHeight + 'px';
			div.style.lineHeight = el.offsetHeight + 'px';
			div.innerHTML = 'Click to maximize';
			div.setAttribute('videoUid', uid);
			div.addEventListener('click', this.handleOverlayClick);
			if (el.nextSibling) {
				el.parentNode.insertBefore(div, el.nextSibling);
			} else {
				el.parentNode.appendChild(div);
			}
			var elRect = el.getBoundingClientRect();
			var divRect = div.getBoundingClientRect();
			var topError = elRect.top - divRect.top;
			var leftError = elRect.left - divRect.left;
			div.style.marginLeft = leftError + 'px';
			div.style.marginTop = topError + 'px';
		},

		// remove overlay button
		removeOverlay: function (uid) {
			var overlays = document.querySelectorAll('.tc-overlay');
			for (var i = 0; i < overlays.length; i++) {
				if (uid === overlays[i].getAttribute('videoUid')) {
					overlays[i].removeEventListener('click', this.handleOverlayClick);
					overlays[i].parentNode.removeChild(overlays[i]);
				}
			}
		},

		// handle click on overlay
		handleOverlayClick: function (e) {
			var uid = e.target.getAttribute('videoUid');
			window.top.postMessage({
				message: 'maximizeVideo',
				path: TC.path.concat([uid])
			}, '*');
			e.preventDefault();
			e.stopPropagation();
		},

		// handle changes in the document (such as occur in playlists when the video is replaced)
		handleMutations: function (mutations) {
			if (!TC.hasClass(document.body, "tc-show")) {
				window.clearTimeout(TC.handleVideoRemoveTimeout);
				TC.handleVideoRemoveTimeout = window.setTimeout(function () {
					TC.handleVideoRemoved()
				}, 100);
			} else {
				mutations.forEach(function (mutation) {
					for (var i in mutation.removedNodes) {
						if (TC.hasClass(mutation.removedNodes[i], 'tc-show')) {
							window.clearTimeout(TC.handleVideoRemoveTimeout);
							TC.handleVideoRemoveTimeout = window.setTimeout(function () {
								TC.handleVideoRemoved()
							}, 100);
						}
					}
				});
			}
		},

		// action to take when the video was removed from the DOM
		handleVideoRemoved: function () {

			// find next video to maximize if current one is removed
			this.findVideos([]);
		},

		// show youtube html5 controls upons mousemove
		handleYTHTML5MouseMove: function (e) {
			var onControls = e.clientY > window.innerHeight - 40;
			var controls = TC.target.YTHTML5controls;
			if (TC.target.controlsVisible) {
				window.clearTimeout(TC.hideControlsTimeout);
			} else {
				controls.setAttribute('style', controls['originalStyle'] + 'position: fixed !important; visibility: visible !important;');
				TC.target.controlsVisible = true;
			}
			TC.hideControlsTimeout = window.setTimeout(function () {
				if (!onControls) {
					controls.setAttribute('style', controls['originalStyle'] + 'position: absolute !important; visibility: hidden !important;');
					TC.target.controlsVisible = false;
				}
			}, 1000);
		},

		// jQuery hasClass function
		hasClass: function (node, selector) {
			var className = " " + selector + " ";
			if (node.nodeType === 1 && (" " + node.className + " ").replace(/[\t\r\n\f]/g, " ").indexOf(className) >= 0) {
				return true;
			} else {
				return false;
			}
		},

		// add class to element and all children (recursively)
		addClassDeep: function (el, cls) {
			if (el.classList) {
				el.classList.add(cls);
			}
			var children = el.childNodes;
			for (var i in children) {
				TC.addClassDeep(children[i], cls);
			}
		},

		// remove class from element and all children (recursively)
		removeClassDeep: function (el, cls) {
			if (el.classList) {
				el.classList.remove(cls);
			}
			var children = el.childNodes;
			for (var i in children) {
				TC.removeClassDeep(children[i], cls);
			}
		},

		// determine if element is currently visible
		isVisible: function (el) {
			return (el.clientWidth > 0 || el.clientHeight > 0) && window.getComputedStyle(el).visibility !== 'hidden';
		},

		getOffset: function (el) {
			var offset = {
				left: 0,
				top: 0
			};
			do {
				offset.left += (el.offsetLeft || 0);
				offset.top += (el.offsetTop || 0);
			} while (el = el.offsetParent);
			return offset;
		},

		fractionInViewport: function (el) {
			var rect = el.getBoundingClientRect();
			var visibleRect = {
				top: Math.max(0, rect.top),
				right: Math.min(window.innerWidth || document.documentElement.clientWidth, rect.right),
				bottom: Math.min(window.innerHeight || document.documentElement.clientHeight, rect.bottom),
				left: Math.max(0, rect.left)
			};
			visibleRect.width = Math.max(0, visibleRect.right - visibleRect.left);
			visibleRect.height = Math.max(0, visibleRect.bottom - visibleRect.top);

			return (visibleRect.width * visibleRect.height) / (rect.width * rect.height);
		},

		getPlayer: function (el) {
			if (el.nodeName.toLowerCase() === 'video') {
				return "html5";
			} else if (el.getPlayerState) {
				return "yt";
			} else if (el.getConfig) {
				return "jw4";
			} else {
				return "other";
			}
		},

		getQuality: function (el) {
			var player = this.getPlayer(el);
			if (player === "yt") {
				try {
					return el.getPlaybackQuality();
				} catch (err) {}
			} else {
				return "";
			}
		},

		setQuality: function (el, q) {
			var player = this.getPlayer(el);
			if (player === "yt") {
				try {
					if (q === 'highest') {
						var qs = el.getAvailableQualityLevels();
						q = qs[0];
					}
					window.setTimeout(function () {
						try {
							el.setPlaybackQuality(q);
						} catch (err) {}
					}, 200);
				} catch (e) {}
			}
		},

		createHTML5Controls: function (node) {
			var html5controls = {

				video: node,
				container: null,

				construct: function () {

					// create container
					this.container = document.createElement('div');
					this.container.classList.add('tc-show');
					this.container.setAttribute('id', 'tc-html5-controls-hover-container');
					this.container.setAttribute('style', 'display: block; text-align: center; width: 100%; height: 50px; position: fixed; bottom: 0; left: 0; z-index: 10000');

					// create inner container
					this.inner = document.createElement('div');
					this.inner.classList.add('tc-show');
					this.inner.setAttribute('style', 'position: relative; width: 50%; padding: 0px 30px; margin: 5px auto; height: 30px; background-color: rgba(0,0,0,0.5); border-radius: 5px; box-shadow: 0px 0px 10px #888;  -webkit-box-sizing: content-box;-moz-box-sizing: content-box;box-sizing: content-box;');
					this.container.appendChild(this.inner);

					// create stretching container
					var stretch = document.createElement('div');
					stretch.classList.add('tc-show');
					stretch.setAttribute('style', 'position: relative; width: 100%;');
					this.inner.appendChild(stretch);

					// add play/pause button
					this.btnPlay = document.createElement('div');
					this.btnPlay.setAttribute('style', 'position: absolute; left: 5px; top: 5px; width: 20px; height: 20px; background-color: rgba(100,100,100,0.5); color: #fff; font-size: 16px; line-height: 20px; border-radius: 3px; cursor: pointer');
					this.btnPlay.classList.add('tc-show');
					this.updateState();
					this.inner.appendChild(this.btnPlay);

					// add progress bar
					this.progressBar = document.createElement('div');
					this.progressBar.classList.add('tc-show');
					this.progressBar.setAttribute('style', 'float: left; position: relative; margin: 5px 0; height: 20px; width: 79%; border: 1px solid rgba(255,255,255,0.2); border-radius: 3px; overflow: hidden;');
					this.loadedIndicator = document.createElement('div');
					this.loadedIndicator.classList.add('tc-show');
					this.loadedIndicator.setAttribute('style', 'position: absolute; left: 0; top: 0; width: 0%; height: 100%; background-color: rgba(255,255,255,0.2);');
					this.playedIndicator = document.createElement('div');
					this.playedIndicator.classList.add('tc-show');
					this.playedIndicator.setAttribute('style', 'position: absolute; left: 0; top: 0; width: 0%; height: 100%; background-color: rgba(255,255,255,0.5);');
					this.seekRange = document.createElement('input');
					this.seekRange.classList.add('tc-show');
					this.seekRange.setAttribute('type', 'range');
					this.seekRange.setAttribute('min', 0);
					this.seekRange.setAttribute('max', 100);
					this.seekRange.setAttribute('step', 0.1);
					this.seekRange.setAttribute('style', 'position: relative; width: 100%; background: none; border: none;');
					this.progressBar.appendChild(this.loadedIndicator);
					this.progressBar.appendChild(this.playedIndicator);
					this.progressBar.appendChild(this.seekRange);
					stretch.appendChild(this.progressBar);

					// add volume section container
					var volumeContainer = document.createElement('div');
					volumeContainer.classList.add('tc-show');
					volumeContainer.setAttribute('style', 'float: left; position: relative; margin: 5px 0; height: 20px; width: 20%;');
					stretch.appendChild(volumeContainer);

					// add mute button
					this.btnMute = document.createElement('div');
					this.btnMute.classList.add('tc-show');
					this.btnMute.setAttribute('style', 'position: absolute; width: 20px; height: 20px; left: 5px; top: 0; background-color: rgba(100,100,100,0.5); color: #fff; font-size: 12px; line-height: 20px; border-radius: 3px; text-align: center; cursor: pointer');
					this.btnMute.appendChild(document.createTextNode('\u25C1)'));
					volumeContainer.appendChild(this.btnMute);

					// add volumeRange bar
					this.volumeBar = document.createElement('div');
					this.volumeBar.classList.add('tc-show');
					this.volumeBar.setAttribute('style', 'position: absolute; left:30px; top: 0; right: 0; bottom: 0; border: 1px solid rgba(255,255,255,0.2); border-radius: 3px; overflow: hidden;');
					this.volumeIndicator = document.createElement('div');
					this.volumeIndicator.classList.add('tc-show');
					this.volumeIndicator.setAttribute('style', 'position: absolute; left: 0; top: 0; width: 0%; height: 100%; background-color: rgba(255,255,255,0.2);');
					this.volumeRange = document.createElement('input');
					this.volumeRange.classList.add('tc-show');
					this.volumeRange.setAttribute('type', 'range');
					this.volumeRange.setAttribute('min', 0);
					this.volumeRange.setAttribute('max', 100);
					this.volumeRange.setAttribute('step', 1);
					this.volumeRange.setAttribute('style', 'position: relative; width: 100%; background: none; border: none;');
					this.updateVolume();
					this.volumeBar.appendChild(this.volumeIndicator);
					this.volumeBar.appendChild(this.volumeRange);
					volumeContainer.appendChild(this.volumeBar);

					// add TabCinema button
					this.btnTC = document.createElement('div');
					this.btnTC.classList.add('tc-show');
					this.btnTC.setAttribute('style', 'position: absolute; right: 5px; top: 5px; width: 20px; height: 20px; background-color: rgba(100,100,100,0.5); color: #fff; font-size: 18px; line-height: 20px; border-radius: 3px; cursor: pointer');
					this.btnTC.appendChild(document.createTextNode('\u25A3'));
					this.inner.appendChild(this.btnTC);

					// add update intervals
					this.updatePlayedInterval = window.setInterval(this.updatePlayed, 40);

					// add event handlers
					this.btnPlay.addEventListener('mousedown', this.togglePlayPause);
					this.btnPlay.addEventListener('click', this.doNothing);
					this.btnTC.addEventListener('mousedown', this.exitFullScreen);
					this.btnTC.addEventListener('click', this.doNothing);
					this.seekRange.addEventListener('mousedown', this.seekRangeMouseDown);
					this.seekRange.addEventListener('click', this.doNothing);
					this.seekRange.addEventListener('change', this.setSeek);
					this.seekRange.addEventListener('mouseup', this.seekRangeMouseUp);
					this.volumeRange.addEventListener('mousedown', this.setVolume);
					this.volumeRange.addEventListener('click', this.doNothing);
					this.volumeRange.addEventListener('change', this.setVolume);
					this.volumeRange.addEventListener('mouseup', this.setVolume);
					this.btnMute.addEventListener('mousedown', this.toggleMute);
					this.btnMute.addEventListener('click', this.doNothing);

					// add video event listeners
					this.video.addEventListener('progress', this.updateLoaded);
					this.video.addEventListener('play', this.updateState);
					this.video.addEventListener('pause', this.updateState);
				},

				doNothing: function(e) {
					e.stopPropagation();
				},

				togglePlayPause: function (e) {
					var newState = !html5controls.video.paused;
					if (newState) {
						html5controls.video.pause();
					} else {
						html5controls.video.play();
					}
					e.stopPropagation();
				},

				seekRangeMouseDown: function (e) {
					html5controls.wasPaused = html5controls.video.paused;
					html5controls.video.pause();
					e.stopPropagation();
				},

				seekRangeMouseUp: function (e) {
					if (!html5controls.wasPaused) {
						html5controls.video.play();
					}
					e.stopPropagation();
				},

				setSeek: function (e) {
					html5controls.video.currentTime = this.value / 100 * html5controls.video.duration;
					e.stopPropagation();
				},

				toggleMute: function (e) {
					html5controls.video.muted = !html5controls.video.muted;
					html5controls.updateVolume();
					e.stopPropagation();
				},

				setVolume: function (e) {
					if (html5controls.video.muted) {
						html5controls.toggleMute();
					}
					html5controls.video.volume = this.value / 100;
					html5controls.updateVolume();
					e.stopPropagation();
				},

				exitFullScreen: function (e) {
					window.top.postMessage({
						message: 'requestEndFullscreen'
					}, '*');
					e.stopPropagation();
				},

				updateState: function () {
					var isPaused = html5controls.video.paused;
					while (html5controls.btnPlay.childNodes.length) {
						html5controls.btnPlay.removeChild(html5controls.btnPlay.firstChild);
					}
					var str = isPaused ? '\u25BA' : '\u25AE\u25AE';
					html5controls.btnPlay.appendChild(document.createTextNode(str));
				},

				updateVolume: function () {
					if (html5controls.video.muted) {
						html5controls.btnMute.style['backgroundColor'] = 'rgba(200,50,50,0.5)';
						html5controls.volumeRange.value = 0;
						html5controls.volumeIndicator.style.width = '0%';
					} else {
						html5controls.btnMute.style['backgroundColor'] = 'rgba(100,100,100,0.5)';
						html5controls.volumeRange.value = html5controls.video.volume * 100;
						html5controls.volumeIndicator.style.width = html5controls.video.volume * 100 + '%';
					}
				},

				updateLoaded: function () {
					var tr = html5controls.video.buffered;
					for (var i = 0; i < tr.length; i++) {
						var ti = tr.start(i);
						var tf = tr.end(i);
					}
					html5controls.loadedIndicator.style.width = 100 * tf / html5controls.video.duration + '%';
				},

				updatePlayed: function () {
					html5controls.playedIndicator.style.width = 100 * html5controls.video.currentTime / html5controls.video.duration + '%';
					html5controls.seekRange.value = 100 * html5controls.video.currentTime / html5controls.video.duration;
				},

				destroy: function () {

					// clear intervals
					window.clearInterval(this.updatePlayedInterval);

					// strip event handlers
					this.btnPlay.removeEventListener('mousedown', this.togglePlayPause);
					this.btnPlay.removeEventListener('click', this.doNothing);
					this.btnTC.removeEventListener('mousedown', this.exitFullScreen);
					this.btnTC.removeEventListener('click', this.doNothing);
					this.seekRange.removeEventListener('mousedown', this.seekRangeMouseDown);
					this.seekRange.removeEventListener('click', this.doNothing);
					this.seekRange.removeEventListener('change', this.setSeek);
					this.seekRange.removeEventListener('mouseup', this.seekRangeMouseUp);
					this.volumeRange.removeEventListener('mousedown', this.setVolume);
					this.volumeRange.removeEventListener('click', this.doNothing);
					this.volumeRange.removeEventListener('change', this.setVolume);
					this.volumeRange.removeEventListener('mouseup', this.setVolume);
					this.btnMute.removeEventListener('mousedown', this.toggleMute);
					this.btnMute.removeEventListener('click', this.doNothing);

					// strip video event listeners
					this.video.removeEventListener('progress', this.updateLoaded);
					this.video.removeEventListener('play', this.updateState);
					this.video.removeEventListener('pause', this.updateState);

					// remove controls from DOM tree
					this.container.parentNode.removeChild(this.container);
					html5controls = null;
				}
			};

			html5controls.construct();

			return html5controls;
		}
	};
}